﻿import sqlite3 as sqlite
from PTable import PTable


class PDataBase(object):
    """
    Класс-обёртка над API работы с базами данных посредством SQLite.
    """

    # Отображение типов данных в Python на типы данных в SQLite.
    _dataTypes = ('INTEGER', 'BOOL', 'REAL', 'VARCHAR', 'TEXT', 'BLOB', 'DATE', 'TIMESTAMP')

    @staticmethod
    def getDataTypes():
        """
        Возвращает кортеж типов данных в Python, которые можно хранить в базе данных.
        """
        return PDataBase._dataTypes

    def __init__(self, connectionString: str):
        """
        Создаёт экземпляр базы данных SQLite находящейся в укзанном
        в connectionString файле. При отсутствии файла создаёт новую БД.
        """
        self._connection = None
        try:
            # Подключение к указанной базе данных SQLite.
            self._connection = sqlite.connect(connectionString, detect_types = sqlite.PARSE_DECLTYPES)
            self._connection.execute('PRAGMA foreign_keys = ON')   # Установка поддержки внешнего ключа.
            cursor = self._connection.cursor()                     # Создание курсора.
            cursor.execute("SELECT name FROM sqlite_master")       # Получим имена всех таблиц в базе данных.
            # Тут будут храниться объекты-обёртки таблиц из текущей базы данных.
            self._tables = [PTable(name[0], self._connection) for name in cursor.fetchall()]
        except sqlite.Error as ex:
            raise ex
    
    def getTable(self, name: str):
        """
        Получение таблицы с указанным именем.
        В случае, если такой таблицы не существует в данной БД, выбрасывается исключение.
        """
        for t in self._tables:
            if t.name == name:
                return t
        else:
            raise sqlite.Error("Таблицы с именем '{}' нет в БД.".format(name))

    def tablesNames(self):
        """
        Получение имен всех таблиц в данной БД.
        """
        return [t.name for t in self._tables]

    def itBelongsTable(self, name: str):
        """
        Проверка существование(принадлежности) таблицы с указанным именем к данной БД.
        """
        return name in self.tablesNames()

    def createTable(self, name: str, columns: list, primaryKeys=[], foreignKeys=[]):
        """
        Создание таблицы с указанным наименованием и структрурой. Если таблица с таким именем уже существует,
        то изменений в БД не произойдет и возращается уже созданная таблица.
        # name (str) - имя таблицы.
        # columns (list) - столбцы, заданные следующим образом: [(номер_столбца, имя_столбца, тип_столбца),].
        # primaryKeys (list) - первичный ключ/ ключи(в случае если составной ключ),
            заданные следующим образом:: [имя_столбца,].
        # foreignKeys (list) - вторичные ключи (внешние), по умолчанию отсутствуют,
            заданные следующим образом:: [(имя_столбца_внешний_ключ, имя_внешней_таблици, имя_столбца_первичный_ключ_внешней_таблици),]
        # return (PTable) - созданная таблица.
        """
        try:
            query = 'CREATE TABLE IF NOT EXISTS `{}` ({}{}{})'.format(
                name,
                ', '.join('`{}` {}'.format(col[1], col[2] if (col[2] != 'VARCHAR' and col[2] != 'TEXT') else '{} COLLATE NOCASE'.format('VARCHAR(50)' if col[2] == 'VARCHAR' else 'TEXT')) for col in sorted(columns, key=lambda col: col[0])),
                '' if len(primaryKeys) == 0 else ', PRIMARY KEY({})'.format(', '.join('`{}`'.format(pk) for pk in primaryKeys)),
                '' if len(foreignKeys) == 0 else ' '.join(', FOREIGN KEY(`{}`) REFERENCES `{}`(`{}`) ON DELETE CASCADE'.format(*fk) for fk in foreignKeys)
            )
            cursor = self._connection.cursor()                  # Создание курсора.
            cursor.execute(query)                               # Выполнение запроса.
            self._connection.commit()                           # Подтверждение изменений.
            newTable = PTable(name, self._connection)           # Создание таблицы-обертки.
        except sqlite.Error as ex:
            raise ex

        self._tables.append(newTable) # Добавление таблицы в список всех таблиц БД.
        return newTable

    def deleteTable(self, name: str):
        """
        Удаление таблицы с указанным именем
        в случае если такой таблицы не существует в данном БД выбрасывается исключение.
        """
        # Удаление таблицы непосредственно из БД.
        try:
            cursor = self._connection.cursor()
            cursor.execute("DROP TABLE `{}`".format(name))
            self._connection.commit() # Подтверждение изменений.
        except sqlite.Error as ex:
            raise ex
        # Удаление таблицы из класса-обертки.
        for index, table in enumerate(self._tables):
            if table.name == name:
                self._tables.pop(index)
                break

    def selectByQuery(self, fromTables='', select='', where=''):
        """
        Выборка в БД таблицах данных по составленному запросу.
        fromTables - str, имена таблиц откуда будет происходить выборка.
        select - str, поля для которых будет осуществленна выборка, если строка пуста, то предполагается, что для всех полей.
        where - str, относительно которых будет осуществлена выбока в таблице, может быть пустым.
        return - кортежи найденных значений.
        """
        try:
            query = 'SELECT ' + (select if len(select) != 0 else '*') + ' FROM ' + fromTables + ((' WHERE ' + where) if len(where) != 0 else '')
            cursor = self._connection.cursor() # Получение курсора.
            cursor.execute(query)
            return cursor.fetchall()
        except sqlite.Error as ex:
            raise ex